package com.yugao.fintech.draper.gateway.listener;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import com.yugao.fintech.draper.auth.dto.RouteConfVO;
import com.yugao.fintech.draper.auth.mq.RouteMessage;
import com.yugao.fintech.draper.message.queue.support.broadcast.AbstractRedisBroadcastMessageListener;
import com.yugao.fintech.draper.core.constant.CacheConstants;
import com.yugao.fintech.draper.gateway.dynamic.route.CustomRouteDefinition;
import com.yugao.fintech.draper.gateway.dynamic.route.CustomRouteDefinitionRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springdoc.core.AbstractSwaggerUiConfigProperties;
import org.springdoc.core.SwaggerUiConfigProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 监听路由刷新事件
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class RouteMessageConsumer extends AbstractRedisBroadcastMessageListener<RouteMessage> {
    private final RedisTemplate redisTemplate;
    private final CustomRouteDefinitionRepository CustomRouteDefinitionRepository;
    private final SwaggerUiConfigProperties swaggerUiConfigProperties;

    private static final String API_URI = "/%s/v3/api-docs";

    @Override
    public void onMessage(RouteMessage message) {
        // 清空缓存中的路由信息
        redisTemplate.delete(CacheConstants.ROUTE_KEY);
        if (log.isDebugEnabled()) {
            log.debug("接收到redis topic消息，缓存路由信息到redis {}", message.getRouteConfList());
        }
        List<RouteConfVO> routeConfList = message.getRouteConfList();
        Map<String, CustomRouteDefinition> map = routeConfList.stream()
                .map(this::convert)
                .collect(Collectors.toMap(CustomRouteDefinition::getId, Function.identity()));
        redisTemplate.opsForHash().putAll(CacheConstants.ROUTE_KEY, map);
        CustomRouteDefinitionRepository.publishEvent();
        if (CollectionUtil.isEmpty(map)) {
            return;
        }
        Set<AbstractSwaggerUiConfigProperties.SwaggerUrl> urls = map.values().stream().map(routeDefinition -> {
            AbstractSwaggerUiConfigProperties.SwaggerUrl swaggerUrl = new AbstractSwaggerUiConfigProperties.SwaggerUrl();
            String id = routeDefinition.getId();
            swaggerUrl.setName(id);
            swaggerUrl.setUrl(String.format(API_URI,
                    routeDefinition.getId().substring(routeDefinition.getId().lastIndexOf("-") + 1)));
            return swaggerUrl;
        }).collect(Collectors.toSet());
        swaggerUiConfigProperties.setUrls(urls);
    }

    public CustomRouteDefinition convert(RouteConfVO dto) {
        CustomRouteDefinition routeDefinition = new CustomRouteDefinition();
        PropertyMapper mapper = PropertyMapper.get().alwaysApplyingWhenNonNull();
        mapper.from(dto.getName()).whenNonNull().to(routeDefinition::setName);
        mapper.from(dto.getRouteId()).whenNonNull().to(routeDefinition::setId);
        mapper.from(dto.getUri()).whenNonNull().as(URI::create).to(routeDefinition::setUri);
        mapper.from(dto.getSortOrder()).whenNonNull().as(Integer::valueOf).to(routeDefinition::setOrder);
        mapper.from(dto.getMetadata())
                .whenNonNull()
                .as(v -> JSONUtil.toBean(v, Map.class))
                .to(routeDefinition::setMetadata);
        mapper.from(dto.getFilters())
                .whenNonNull()
                .as(JSONArray::new)
                .as(v -> v.toList(FilterDefinition.class))
                .to(routeDefinition::setFilters);
        mapper.from(dto.getPredicates())
                .whenNonNull()
                .as(JSONArray::new)
                .as(v -> v.toList(PredicateDefinition.class))
                .to(routeDefinition::setPredicates);
        return routeDefinition;
    }
}